﻿module('When creating a new Puzzle',
{
    setup: function()
    {
        puzzle = new Puzzle();
    }
});

test('The Puzzle should have 4 Boxes', function()
{
    equals(puzzle.getAllBoxes().length, 4);
});

test('The Boxes should be in a 2x2 grid', function()
{
    for (var columnIndex = 0; columnIndex < 2; columnIndex++)
    {
        for (var rowIndex = 0; rowIndex < 2; rowIndex++)
        {
            var box = puzzle.getBox(columnIndex, rowIndex);
            equals(box.getColumnIndex(), columnIndex);
            equals(box.getRowIndex(), rowIndex);
        }
    }
});

test('No Boxes should be selected', function()
{
    equals(puzzle.getSelectedBox(), null);
});

test('No Boxes should have a value', function()
{
    var boxes = puzzle.getAllBoxes();
    for (var i = 0; i < boxes.length; i++)
        equals(boxes[i].getValue(), null);
});

module('When a Box is clicked on',
{
    setup: function()
    {
        puzzle = new Puzzle('unsel', 'sel');
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    }
});

test('The clicked Box should be the only selected Box in the Puzzle', function()
{
    equals(puzzle.getBox(0, 0).getIsSelected(), false);
    puzzle.getBox(0, 0).toggleSelection();
    equals(puzzle.getBox(0, 0).getIsSelected(), true);
});

test('The clicked Box should have the selected CSS class', function()
{
    puzzle.getBox(0, 0).toggleSelection();
    equals(puzzle.getBox(0, 0).getElement().className, 'sel');
});

test('The unselected Boxes should have the unselected CSS class', function()
{
    var box = puzzle.getBox(0, 0);
    box.toggleSelection();

    var boxes = puzzle.getAllBoxes();
    for (var i = 0; i < boxes.length; i++)
    {
        if (boxes[i] != box)
            equals(boxes[i].getElement().className, 'unsel');
    }
});

test('The Box that was previously selected should now have the unselected CSS class', function()
{
    puzzle.getBox(0, 0).toggleSelection();
    equals(puzzle.getBox(0, 0).getElement().className, 'sel');
    puzzle.getBox(0, 1).toggleSelection();
    equals(puzzle.getBox(0, 0).getElement().className, 'unsel');
    equals(puzzle.getBox(0, 1).getElement().className, 'sel');
});

module('When the user presses a key',
{
    setup: function()
    {
        puzzle = new Puzzle();
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    }
});

test('The value should be displayed in the Box if the value is valid', function()
{
    puzzle.getBox(0, 0).toggleSelection();
    puzzle.onKeyDown(49); // '1'
    equals(puzzle.getSelectedBox().getValue(), 1);
    puzzle.onKeyDown(50); // '2'
    equals(puzzle.getSelectedBox().getValue(), 2);
    puzzle.onKeyDown(51); // '3'
    equals(puzzle.getSelectedBox().getValue(), 3);
    puzzle.onKeyDown(52); // '4'
    equals(puzzle.getSelectedBox().getValue(), 4);
});

test('The value should be ignored if no Box is selected', function()
{
    puzzle.onKeyDown(49); // '1'
    var boxes = puzzle.getAllBoxes();
    for (var i = 0; i < boxes.length; i++)
    {
        equals(boxes[i].getValue(), null);
    }
});

test('The value in a Box should be cleared if the user hits the Backspace key', function()
{
    puzzle.getBox(0, 0).toggleSelection();
    puzzle.onKeyDown(49); // '1'
    equals(puzzle.getSelectedBox().getValue(), 1);
    puzzle.onKeyDown(8); // 8 = ASCII code for Backspace key
    equals(puzzle.getSelectedBox().getValue(), null);
});

test('The value in a Box should be cleared if the user hits the Delete key', function()
{
    puzzle.getBox(0, 0).toggleSelection();
    puzzle.onKeyDown(49); // '1'
    equals(puzzle.getSelectedBox().getValue(), 1);
    puzzle.onKeyDown(46); // Delete key
    equals(puzzle.getSelectedBox().getValue(), null);
});

module('When setting the value in a Box',
{
    setup: function()
    {
        box = new Box(0, 0);
        var element = new Object();
        element.innerHTML = null;
        box.setElement(element);
    }
});

test('The value in the Box should be cleared if the value is null', function()
{
    box.setValue(null);
    equals(box.getValue(), null);
    equals(box.getElement().innerHTML, '');
});

test('An exception should be thrown if the value is not valid', function()
{
    try
    {
        box.setValue('a');
        equals(1, 2, "Expected exception was not thrown");
    }
    catch (e)
    {
        equals(e != null, true);
    }
});

test('The value should be set if the value is valid', function()
{
    for (var i = 1; i <= 4; i++)
    {
        box.setValue(i);
        equals(box.getValue(), i);
        equals(box.getElement().innerHTML, i);
    }
});

module('When assigning an element to a Box',
{
    setup: function()
    {
        box = new Box(0, 0);
    }
});

test('The element should have the Box assigned to the box property', function()
{
    var element = function() { };
    box.setElement(element);
    equals(element.box, box);
});

module('When toggling the selection of a box',
{
    setup: function()
    {
        box = new Box(0, 0);
        box.setElement(function() { this.className = ''; });
    }
});

test('The value should change from selected to not selected and vice versa', function()
{
    var isSelected = box.getIsSelected();
    box.toggleSelection();
    equals(box.getIsSelected(), !isSelected);
    box.toggleSelection();
    equals(box.getIsSelected(), isSelected);
});

test('Listeners should be notified', function()
{
    var testValue = null;
    box.onIsSelectedChanged(function(sender) { testValue = 10; });
    box.toggleSelection();
    equals(testValue, 10);
});

module('When validating a puzzle',
{
    setup: function()
    {
        puzzle = new Puzzle();
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    }
});

test('The Puzzle is invalid if any Box does not have a value', function()
{
    puzzle.setAllValuesToOneExcept = function(box)
    {
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
        {
            if (boxes[i] == box)
                boxes[i].setValue(null);
            else
                boxes[i].setValue(i + 1);
        }
    };

    for (var column = 0; column < 2; column++)
    {
        for (var row = 0; row < 2; row++)
        {
            puzzle.setAllValuesToOneExcept(puzzle.getBox(column, row));
            equals(puzzle.isPuzzleValid(), false);
        }
    }
});

test('The Puzzle is invalid if any Box has the same value as another Box', function()
{
    puzzle.setDuplicateValue = function(box)
    {
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
        {
            if (boxes[i] == box)
                boxes[i].setValue(i == 3 ? 1 : i + 2);
            else
                boxes[i].setValue(i + 1);
        }
    };

    for (var column = 0; column < 2; column++)
    {
        for (var row = 0; row < 2; row++)
        {
            puzzle.setDuplicateValue(puzzle.getBox(0, 0));
            equals(puzzle.isPuzzleValid(), false);
        }
    }
});

test('The Puzzle is valid if all Boxes have unique values', function()
{
    var boxes = puzzle.getAllBoxes();
    for (var i = 0; i < boxes.length; i++)
    {
        boxes[i].setValue(i + 1);
    }
    equals(puzzle.isPuzzleValid(), true);
});

test('Should show an alert box with the result', function()
{
    var alertMessage;
    puzzle.showAlert = function(message) { alertMessage = message; };
    puzzle.validate();
    equals(alertMessage, 'The puzzle is invalid.');
});

module('When retrieving a new Puzzle',
{
    setup: function()
    {
        puzzle = new Puzzle();
        puzzle.getNewPuzzleDataSynchronously = true;
        var boxes = puzzle.getAllBoxes();
        for (var i = 0; i < boxes.length; i++)
            boxes[i].setElement(function() { this.className = null; });
    }
});

test('Should load the information for a new Puzzle', function()
{
    var boxHasData = false;
    puzzle.getNewPuzzleData();
    var allBoxes = puzzle.getAllBoxes();
    for (var i = 0; i < allBoxes.length; i++)
    {
        if (allBoxes[i].getValue() >= 1 && allBoxes[i].getValue() <= 4)
            boxHasData = true;
    }

    equals(boxHasData, true);
});

test('All Boxes that are not assigned a value in the new Puzzle should have no value', function()
{
    for (var columnIndex = 0; columnIndex < 2; columnIndex++)
    {
        for (var rowIndex = 0; rowIndex < 2; rowIndex++)
        {
            puzzle.getBox(columnIndex, rowIndex).setValue(4);
        }
    }

    puzzle.getNewPuzzleData = function()
    {
        var result = new Array();
        result.push(new Array(0, 0, 1));
        result.push(new Array(1, 0, 3));
        puzzle.loadNewPuzzleData(result);
    };

    puzzle.loadNewPuzzle();
    equals(puzzle.getBox(0, 0).getValue(), 1);
    equals(puzzle.getBox(0, 1).getValue(), null);
    equals(puzzle.getBox(1, 0).getValue(), 3);
    equals(puzzle.getBox(1, 1).getValue(), null);
});

test('Should load the new puzzle information into the current Puzzle', function()
{
    puzzle.getNewPuzzleData = function()
    {
        var result = new Array();
        result.push(new Array(0, 0, 1));
        result.push(new Array(0, 1, 2));
        result.push(new Array(1, 0, 3));
        result.push(new Array(1, 1, 4));
        puzzle.loadNewPuzzleData(result);
    };

    puzzle.loadNewPuzzle();
    equals(puzzle.getBox(0, 0).getValue(), 1);
    equals(puzzle.getBox(0, 1).getValue(), 2);
    equals(puzzle.getBox(1, 0).getValue(), 3);
    equals(puzzle.getBox(1, 1).getValue(), 4);
});

